/* Copyright (c) 2022-2023, Arm Limited and Contributors. All rights reserved.
 * SPDX-License-Identifier: Apache-2.0
 */

#include "cmsis_os2.h"
#include "iot_bluetooth_hci.h"

#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>

#include "host/ble_hs_log.h"
#include "os/os_mbuf.h"
#include "os/queue.h"

/* BLE */
#include "nimble/ble.h"
#include "nimble/hci_common.h"
#include "nimble/nimble_npl.h"
#include "nimble/transport.h"
#include "nimble/transport/hci_h4.h"

#define HCI_CMD_OFFSET_TYPE   0
#define HCI_CMD_OFFSET_OPCODE 1
#define HCI_CMD_OFFSET_LENGTH 3
#define HCI_CMD_OFFSET_DATA   4

// we use it allocate buffer in increments of 32
#define ALIGN_SIZE_TO_32(new_size) ((new_size == 0) ? 0 : ((((new_size - 1) / 32) + 1) * 32))

static struct hci_h4_sm hci_cdi_h4sm;

// not thread safe
static uint8_t *get_internal_buffer(size_t new_size)
{
    static size_t internal_hci_message_buffer_size = 0;
    static uint8_t *internal_hci_message_buffer = NULL;

    if (internal_hci_message_buffer == NULL) {
        internal_hci_message_buffer_size = ALIGN_SIZE_TO_32(new_size);
        internal_hci_message_buffer = malloc(internal_hci_message_buffer_size);
    } else if (new_size > internal_hci_message_buffer_size) {
        internal_hci_message_buffer_size = ALIGN_SIZE_TO_32(new_size);
        uint8_t *new_buffer = realloc(internal_hci_message_buffer, internal_hci_message_buffer_size);
        if (new_buffer == NULL) {
            free(internal_hci_message_buffer);
        }
        internal_hci_message_buffer = new_buffer;
    }

    return internal_hci_message_buffer;
}

int ble_transport_to_ll_acl_impl(struct os_mbuf *input_mbuf)
{
    iot_hci_message_t msg;

    // count up the chained buffers lengths to get the complete data length
    struct os_mbuf *tmp_mbuf;
    size_t num_of_buffers = 0;
    size_t data_len = 0;
    for (tmp_mbuf = input_mbuf; tmp_mbuf; tmp_mbuf = SLIST_NEXT(tmp_mbuf, om_next)) {
        data_len += tmp_mbuf->om_len;
        num_of_buffers++;
    }

    // All standard HCI packets should be at least 2 Byte long.
    if (data_len < 2) {
        return BLE_NPL_EINVAL;
    }

    msg.type = IOT_HCI_PACKET_TYPE_ACL;
    msg.packet_size = data_len;

    if (num_of_buffers == 1) {
        msg.type_included_in_packet = false;
        msg.packet = input_mbuf->om_data;
    } else {
        msg.packet_size++; // adding the byte for type
        uint8_t *internal_buffer = get_internal_buffer(msg.packet_size);
        if (!internal_buffer) {
            return BLE_ERR_MEM_CAPACITY;
        }
        // packet contains the type as first byte
        msg.type_included_in_packet = true;
        internal_buffer[0] = IOT_HCI_PACKET_TYPE_ACL;
        msg.packet = internal_buffer;

        // copy the chained buffer into a contiguous buffer
        size_t buf_index = 1; // we start past the type
        for (tmp_mbuf = input_mbuf; tmp_mbuf; tmp_mbuf = SLIST_NEXT(tmp_mbuf, om_next)) {
            memcpy(&internal_buffer[buf_index], tmp_mbuf->om_data, tmp_mbuf->om_len);
            buf_index += tmp_mbuf->om_len;
        }
    }

    return (iotHciSendToController(&msg) == osOK) ? BLE_ERR_SUCCESS : BLE_ERR_UNSPECIFIED;
}

int ble_transport_to_ll_cmd_impl(void *input_buf)
{
    struct ble_hci_cmd *input_cmd = input_buf;
    iot_hci_message_t msg;

    msg.packet_size = input_cmd->length + 4 /* 1 byte for type, 2 bytes for opcode, 1 byte for length */;

    uint8_t *internal_buffer = get_internal_buffer(msg.packet_size);
    if (!internal_buffer) {
        return BLE_ERR_MEM_CAPACITY;
    }
    msg.packet = internal_buffer;

    internal_buffer[HCI_CMD_OFFSET_TYPE] = IOT_HCI_PACKET_TYPE_CMD;
    internal_buffer[HCI_CMD_OFFSET_OPCODE] = input_cmd->opcode & 0xFF;
    internal_buffer[HCI_CMD_OFFSET_OPCODE + 1] = input_cmd->opcode >> 8;
    internal_buffer[HCI_CMD_OFFSET_LENGTH] = input_cmd->length;
    memcpy(&internal_buffer[HCI_CMD_OFFSET_DATA], input_cmd->data, input_cmd->length);

    msg.type = IOT_HCI_PACKET_TYPE_CMD;
    msg.type_included_in_packet = true;

    return (iotHciSendToController(&msg) == osOK) ? BLE_ERR_SUCCESS : BLE_ERR_UNSPECIFIED;
}

int32_t hci_inbound_message_callback(const uint8_t *buffer, size_t buffer_size)
{
    int len = hci_h4_sm_rx(&hci_cdi_h4sm, buffer, buffer_size);
    if (len <= 0) {
        return osError;
    }

    return osOK;
}

static int full_frame_cb(uint8_t pkt_type, void *inbound_data)
{
    struct os_mbuf *mbuf = inbound_data;

    int critical_return_value;
    int error;

    switch (pkt_type) {

        case IOT_HCI_PACKET_TYPE_EVT:
            OS_ENTER_CRITICAL(critical_return_value);
            error = ble_transport_to_hs_evt(mbuf);
            OS_EXIT_CRITICAL(critical_return_value);
            break;

        case IOT_HCI_PACKET_TYPE_ACL:
            OS_ENTER_CRITICAL(critical_return_value);
            error = ble_transport_to_hs_acl(mbuf);
            OS_EXIT_CRITICAL(critical_return_value);
            break;
        default:
            error = osError;
            break;
    }

    return error;
}

void ble_transport_ll_init(void)
{
    hci_h4_sm_init(&hci_cdi_h4sm, &hci_h4_allocs_from_ll, full_frame_cb);

    osStatus_t status = iotHciTransportInit(&hci_inbound_message_callback);
    if (status != osOK) {
        BLE_HS_LOG(ERROR, "Failed to init transport to controller");
        abort();
    }
}
